iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 4
5
Modern Web

JavaScript 之旅系列 第 4 篇

JavaScript 之旅 (4):Object.keys() & Object.values() & Object.entries()

  • 分享至 

  • xImage
  •  

有時要將 JS 物件轉成其他資料結構,所以需要迭代物件中的所有 property,過去會用 for-in 陳述句,但只想列舉 own property,就必須用 hasOwnProperty() 才能解決。後來 ES5 新增的 Object.keys() 解決了非 own property 的問題,但還是要手動透過 property name 來存取 value,不方便!終於在 ES2017 (ES8) 新增了 Object.values() 和 Object.entries(),可以直接取得 property value 了!

本篇來介紹 Object.values()、Object.keys() 和 Object.entries(),以及在 ECMAScript spec 是如何定義的。

本文同步發表於 Titangene Blog:JavaScript 之旅 (4):Object.keys() & Object.values() & Object.entries()

「JavaScript 之旅」系列文章發文於:

過去的 for-in statement

在過去只能用 for-in statement (陳述句) 來迭代物件中的每個 enumerable (可列舉的) property (但會忽略 key 為 Symbol 的 property):

let rectangle = {width: 16, height: 9};

for (const key in rectangle) {
  console.log(key, rectangle[key]);
}
// width 16
// height 9

而且 for-in 陳述句也會迭代到繼承的 enumerable property:

let rectangle = {width: 16, height: 9};

let customRectangle = Object.create(rectangle);
customRectangle.color = 'blue';
customRectangle.name = 'myCustom';

for (const key in customRectangle) {
  console.log(key, customRectangle[key]);
}
// color blue
// name myCustom
// width 16
// height 9

若不想列舉繼承的 property (即只列舉 own property),可用 Object.prototype.hasOwnProperty() 來判斷:

for (const key in customRectangle) {
  if (customRectangle.hasOwnProperty(key)) {
    console.log(key, customRectangle[key]);
  }
}
// color blue
// name myCustom

去查 spec 才發現,原來 for-in 陳述句在 ES1 (spec 的定義在 p.54) 就有了。

過度期的 Object.keys()

若以只想取得物件中的所有 property key,那 Object.keys() 就很方便,但是,若想一次拿到 property key 和 property name,應該就可以說 Object.keys() 是過度期吧?因為以現代的角度來看,還有更方便的 Object.entries() (後面會介紹)。

後來在 ES5 新增了 Object.keys(),終於可以擺脫 for-in 陳述句了,終於可以讓 JS 物件 (間接) 使用 Array method 了!

Object.keys() 可直接取得所有 property name。看一個簡單的範例:

let object = {a: 1, b: 2, c: 3};

console.log(Object.keys(object));  // ["a", "b", "c"]

因為 Object.keys() 回傳的是陣列,而 JS 的陣列是迭代物件,所以可以使用 for-of 陳述句來迭代:

let object = {a: 1, b: 2, c: 3};

for (const key of Object.keys(object)) {
  console.log(key);
}
// a
// b
// c

當然也可以用 Array.prototype.forEach() 來迭代:

let object = {a: 1, b: 2, c: 3};

Object.keys(object).forEach(key => {
  console.log(key);
});
// a
// b
// c

如果接續剛剛的範例物件,若不想列舉繼承的 property,可用 Object.keys(),它會幫你自動忽略 Object.prototype.hasOwnProperty() 為 false 的 property:

let rectangle = {width: 16, height: 9};

let customRectangle = Object.create(rectangle);
customRectangle.color = 'blue';
customRectangle.name = 'myCustom';

Object.keys(customRectangle).forEach(key => {
  console.log(key, customRectangle[key]);
});
// color blue
// name myCustom

後面會說明為何 Object.keys() 不用我們手動處理 Object.prototype.hasOwnProperty(),spec 其實都有定義。

而且 Object.keys() 還可搭配多個 Array method 做很多事。以一個情境為例:列出排序過的 property,並以 , 串接所有 property。

先利用 Object.keys() 取得所有 property name,然後透過 Array.prototype.sort() 排序 property name,再用 Array.prototype.join() 串接陣列中的元素:

let letterCount = {b: 2, a: 5, c: 3};
let letters = Object.keys(letterCount)
  .sort()
  .join(', ');

console.log(letters);  // a, b, c

若想知道此物件有幾個 property,可用 Object.keys() 搭配 Array.length 取得:

let object = {a: 1, b: 2, c: 3};

let propertyCount = Object.keys(object).length;
console.log(propertyCount);  // 3

現代的 Object.values() 和 Object.entries()

後來在 ES2017 (ES8) 新增了 Object.values() 和 Object.entries(),終於可以取得 property value 了!以前的 for-in 陳述句和 Object.keys() 都還要自己透過原物件的 property name 來存取 property value。

Object.values() 是直接取得所有 property value,並以陣列回傳。看一個簡單的範例:

let object = {a: 1, b: 2, c: 3};

console.log(Object.values(object));  // [1, 2, 3]

而 Object.entries() 是直接取得所有 property 的 name 和 value,並以陣列回傳。看一個簡單的範例:

let object = {a: 1, b: 2, c: 3};

console.log(Object.entries(object));
// [
//   ["a", 1],
//   ["b", 2],
//   ["c", 3]
// ]

Object.values() 和 Object.entries() 跟 Object.keys() 一樣,都不會迭代到繼承的 property:

let rectangle = {width: 16, height: 9};

let customRectangle = Object.create(rectangle);
customRectangle.color = 'blue';
customRectangle.name = 'myCustom';

Object.entries(customRectangle).forEach(([key, value]) => {
  console.log(key, value);
});
// color blue
// name myCustom

後面會說明為何 Object.values() 和 Object.entries() 都不用我們手動處理 Object.prototype.hasOwnProperty(),spec 其實都有定義。

因為 Object.values() 和 Object.entries() 都是回傳陣列,而 JS 的陣列是迭代物件,所以可以使用 for-of 陳述句或 Array method 進行迭代:

let object = {a: 1, b: 2, c: 3};

for (const [key, value] of Object.entries(object)) {
  console.log(key, value);
}
// a 1
// b 2
// c 3

Object.entries(object).forEach(([key, value]) => {
  console.log(key, value);
});
// a 1
// b 2
// c 3

物件轉陣列

Object.values() 和 Object.entries() 常會搭配 Array method 一起使用,因為 Object.values() 和 Object.entries() 的回傳值都是 Array。下面實際舉個例子:

假設原本的資料是一個物件,一個 property 代表一種水果,key 是水果名稱,value 是數量:

let fruits = {
  apple: 2,
  banana: 3,
  orange: 1
};

如果想計算水果總數,可用 Object.values() 搭配 Array.prototype.reduce() 來處理:

const fruitTotal = Object.values(fruits)
  .reduce((accumulator, fruitCount) => accumulator + fruitCount);

console.log(fruitTotal);  // 6

若希望後續處理資料比較方便,你可能會想更改資料結構,將一種水果變為一個物件,該物件有兩個 property,分別為 name 和 count,此時就可用 Object.entries() 來處理:

let fruitBasket = Object.entries(fruits)
  .map(([fruitName, fruitCount]) => {
    return {name: fruitName, count: fruitCount}
  });

console.log(fruitBasket);
// [
//   {name: "apple", count: 2},
//   {name: "banana", count: 3},
//   {name: "orange", count: 1}
// ]

這個作法有一些好處:

  • 使用 Object.entries() 的回傳值 (即陣列),可搭配各種 Array method 來滿足你的需求 (變成你想要的樣子)
  • 因為 Object.entries() 是產生新的陣列,不會影響到原物件,所以後續不管你用的 Array method 是否會修改該陣列,都不會影響到原物件的資料 (夠 pure)

Object 物件轉 Map 物件

可用 Object.entries() 將物件轉成 Map 物件,將 Object.entries() 回傳的可迭代 entries 傳給 new Map() constructor 即可:

let fruits = {
  apple: 2,
  banana: 3,
  orange: 1
};

let fruitMap = new Map(Object.entries(fruits));

console.log(fruitMap);
// Map(3) {"apple" => 2, "banana" => 3, "orange" => 1}

console.log(fruitMap.get('orange'));  // 1

Sepc 定義

以下是 Object.keys() 在 spec 中的定義:

以下是 Object.values() 在 spec 中的定義:

以下是 Object.entries() 在 spec 中的定義:

Object.values()、Object.keys() 和 Object.entries() 都是在步驟 2 使用 EnumerableOwnPropertyNames(),而步驟 3 都是使用 CreateArrayFromList()。

我們先來看 EnumerableOwnPropertyNames() 的定義:

  • 步驟 4:迭代所有 own property,所以每次迭代的 key 就是 property name
  • 步驟 4.a.i:取得某個 own property
  • 步驟 4.a.ii:own property 不為 undefined,並且是可列舉的 (enumerable) 才會繼續下個步驟
  • 步驟 4.a.ii.1:判斷 kind 為 key 時,會將該 key push 至 properties (即最後我們要取得的回傳值)
  • 步驟 4.a.ii.2.a:取得為該 property name 的 property value
  • 步驟 4.a.ii.2.b:判斷 kind 為 value 時,會將該 value push 至 properties
  • 步驟 4.a.ii.2.c.ii:將 key 和 value 組合成 entry (即 [key, value] )
  • 步驟 4.a.ii.2.c.iii:將 entry push 至 properties
  • 最後回傳 properties

所以步驟 4 就是我們不用手動處理 Object.prototype.hasOwnProperty() 的關鍵!

回來看步驟 3 的 CreateArrayFromList(),下面是它的定義:簡單來說就是建立陣列,將每個 property value 放入陣列中:

polyfill

下面是 MDN 提供的 Object.entries() 的 polyfill:

if (!Object.entries) {
  Object.entries = function(obj) {
    var ownProps = Object.keys(obj),
        i = ownProps.length,
        resArray = new Array(i); // preallocate the Array
    while (i--)
      resArray[i] = [ownProps[i], obj[ownProps[i]]];

    return resArray;
  };
}

其他 polyfill:

資料來源


上一篇
JavaScript 之旅 (3):Exponentiation Operator (指數運算子)
下一篇
JavaScript 之旅 (5):String method - padStart & padEnd
系列文
JavaScript 之旅 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言